package net.butfly.bus.config.parser;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.butfly.albacore.exception.SystemException;
import net.butfly.albacore.utils.ReflectionUtils;
import net.butfly.bus.Constants;
import net.butfly.bus.config.Config;
import net.butfly.bus.config.ConfigParser;
import net.butfly.bus.config.bean.FilterBean;
import net.butfly.bus.config.bean.NodeBean;
import net.butfly.bus.config.bean.RouterBean;
import net.butfly.bus.config.bean.invoker.InvokerBean;
import net.butfly.bus.config.bean.invoker.InvokerConfigBean;
import net.butfly.bus.filter.Filter;
import net.butfly.bus.invoker.Invoker;
import net.butfly.bus.invoker.InvokerFactory;
import net.butfly.bus.policy.Router;
import net.butfly.bus.util.XMLUtils;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class XMLConfigParser extends ConfigParser {
	protected Document document;
	protected Element root;

	@Override
	public Config parse() {
		Config config = new Config();
		config.setFilterBeanList(this.parseFilters(this.elements("filter")));
		config.setNodesMap(this.parseNodes("node"));
		config.setRouter(this.parseRouter());
		config.setBusID(this.root.attributeValue("id"));
		return config;
	}

	public XMLConfigParser(InputStream source) {
		super();
		if (null == source)
			throw new SystemException(Constants.UserError.CONFIG_ERROR, "Bus configurations invalid.");
		try {
			this.document = new SAXReader().read(source);
		} catch (DocumentException e) {
			throw new SystemException(Constants.UserError.CONFIG_ERROR, "Bus configurations invalid.", e);
		}
		this.root = this.document.getRootElement();
	}

	protected Map<String, NodeBean> parseNodes(String xpath) {
		List<Element> elements = this.elements(xpath);
		Map<String, NodeBean> nodesMap = new HashMap<String, NodeBean>();
		if (elements != null) {
			for (Element element : elements) {
				String nodeId = element.attributeValue("id");
				String enabled = element.attributeValue("enabled");
				if (enabled == null || enabled.trim().toUpperCase().equals("TRUE")) {
					logger.info("Node [" + nodeId + "] loading...");
					logAsXml(element);
					nodesMap.put(nodeId, this.createNodeBean(nodeId, element));
					logger.info("Node [" + nodeId + "] loaded.");
				} else {
					logger.info("Node [" + nodeId + "] disabled.");
					logAsXml(element);
				}
			}
		}
		return nodesMap;
	}

	public List<FilterBean> parseFilters(List<Element> filters) {
		List<FilterBean> list = new ArrayList<FilterBean>();
		for (Element filter : filters) {
			FilterBean f = parseFilter(filter);
			if (null != f)
				list.add(f);
		}
		return list;
	}

	@SuppressWarnings("unchecked")
	private FilterBean parseFilter(Element filter) {
		String title = filter.attributeValue("title");
		String attr = filter.attributeValue("enabled");
		if (null == attr || Boolean.parseBoolean(attr.toLowerCase())) {
			attr = filter.attributeValue("order");

			Map<String, String> params = new HashMap<String, String>();
			for (Element param : (List<Element>) filter.selectNodes("param"))
				params.put(param.attributeValue("name"), param.attributeValue("value"));

			Class<? extends Filter> clazz;
			try {
				clazz = (Class<? extends Filter>) Class.forName(filter.attributeValue("class"));
			} catch (Throwable e) {
				throw new SystemException(Constants.UserError.FILTER_INVOKE, "Filter class invalid", e);
			}
			FilterBean f = new FilterBean(title, clazz, params);
			logger.info("Filter [" + title + "] enbled.");
			logAsXml(filter);
			return f;
		} else {
			logger.debug("Filter [" + title + "] disabled.");
			logAsXml(filter);
			return null;
		}
	}

	protected static void logAsXml(Element element) {
		logger.debug(XMLUtils.format(element.asXML()).replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "")
				.replaceAll("\n$$", ""));
	}

	@SuppressWarnings("unchecked")
	protected List<Element> elements(String xpath) {
		return root.selectNodes(xpath);
	}

	protected Element element(String xpath) {
		return (Element) root.selectSingleNode(xpath);
	}

	@SuppressWarnings("unchecked")
	protected RouterBean parseRouter() {
		Element element = this.element("router");
		if (element == null)
			return null;
		try {
			Class<? extends Router> routeClass = (Class<? extends Router>) Class.forName(element.attributeValue("type"));
			return new RouterBean(routeClass);
		} catch (Throwable th) {
			throw new SystemException(Constants.UserError.CONFIG_ERROR,
					"Route setting error: can't parse route/policy class name.", th);
		}
	}

	protected NodeBean createNodeBean(String nodeId, Element nodeEle) {
		InvokerBean invokerBean = this.parseInvoker(nodeId, nodeEle);
		return new NodeBean(nodeId, nodeEle.attributeValue("tx"), invokerBean);
	};

	private InvokerBean parseInvoker(String nodeId, Element nodeEle) {
		String id = nodeEle.attributeValue("invoker");
		Element element;
		if (id != null) {
			element = (Element) this.root.selectSingleNode("invoker[@id='" + id + "']");
			if (element == null)
				return null;
		} else {
			element = (Element) nodeEle.selectSingleNode("invoker");
			if (element == null)
				return null;
		}
		Class<? extends Invoker<?>> clazz = classForName(element.attributeValue("type"));
		logger.debug("Node [" + nodeId + "] contains invoker [" + element.attributeValue("type") + "].");
		logAsXml(element);
		InvokerConfigBean config = InvokerFactory.getConfig(clazz);
		processConfigObj(config, element);
		logger.info("Invoker " + clazz.getName() + " parsed.");
		return new InvokerBean(id, clazz, config);
	}

	@SuppressWarnings("unchecked")
	private void processConfigObj(InvokerConfigBean config, Element element) {
		Class<?> clazz = config.getClass();
		while (!clazz.equals(Object.class)) {
			for (Field f : clazz.getDeclaredFields()) {
				if (List.class.isAssignableFrom(f.getType())) {
					List<Object> values = new ArrayList<Object>();
					for (Element ele : (List<Element>) element.selectNodes(f.getName())) {
						Object to = XMLUtils.parseObject(ele);
						if (to != null)
							values.add(to);
					}
					ReflectionUtils.safeFieldSet(f, config, values);
				} else {
					Object value = XMLUtils.parseObject((Element) element.selectSingleNode(f.getName()));
					if (value != null)
						ReflectionUtils.safeFieldSet(f, config, value);
				}
			}
			clazz = clazz.getSuperclass();
		}
	}

}
